/**
 * 做的修改
 * 增加断线重连机制
 */

var WS_CONNECTING = 0;       // ws 已经连接
var WS_OPEN       = 1;       // ws 已经打开
var WS_CLOSING    = 2;       // ws 正在关闭
var WS_CLOSED     = 3;       // ws 已经关闭

var Events = {
  "ERROR"             : "error",              // 错误
  "IOERROR"           : "ioError",            // io错误
  "HEARTBEAT_TIMEOUT" : "heartbeatTimeout",   // 心跳超时
  "CLOSE"             : "close",              // 断开连接，onKick,服务端关闭，disconnect都会触发
  "PARSE_ERROR"       : "parseError",         // 解析服务端返回时发生异常
  "ONKICK"            : "onKick"              // 服务器主动踢出
};

var pomeloProtos = null;                      // 握手数据，可以使用外部传入
// var localStorage = cc.sys.localStorage || window.localStorage;
var EventEmitter = require("./EventEmitter");
var Protobuf     = require("./protobuf");
var Protocol     = require("./protocol");
let WebSocket    = require('ws');

(function() {
  var JS_WS_CLIENT_TYPE    = 'js-websocket';
  var JS_WS_CLIENT_VERSION = '0.0.1';

  var Package  = Protocol.Package;
  var Message  = Protocol.Message;

  var RES_OK         = 200;
  var RES_FAIL       = 500;
  var RES_OLD_CLIENT = 501;

  if (typeof Object.create !== 'function') {
    Object.create = function (o) {
      function F() {}
      F.prototype = o;
      return new F();
    };
  }

  var pomelo = Object.create(EventEmitter.prototype); // object extend from object

  pomelo.Events = Events;

  var socket       = null;
  var reqId        = 0;
  var callbacks    = {};
  var handlers     = {};

  //Map from request id to route
  var routeMap     = {};
  var dict         = {};    // route string to code
  var abbrs        = {};   // code to route string
  var serverProtos = {};
  var clientProtos = {};
  var protoVersion = 0;

  // 心跳处理
  var heartbeatInterval    = 0;
  var heartbeatTimeout     = 0;
  var nextHeartbeatTimeout = 0;
  var gapThreshold         = 100;   // heartbeat gap threashold
  var heartbeatId          = null;
  var heartbeatTimeoutId   = null;
  var heartbeatStartTime   = 0;
  var heartbeatPing        = 0;
  var handshakeCallback    = null;

  var decode = null;
  var encode = null;

  var handshakeBuffer = {
    'sys': {
      type   : JS_WS_CLIENT_TYPE,
      version: JS_WS_CLIENT_VERSION
    },
    'user': {
    }
  };

  var initCallback = null;

  var ws_url    = "";

  var connected  = false;      // 是否连接成功
  var closeCount = 0;          // 连接断开次数

  /**
   * 初始化连接的函数
   * @param params{Object}  eg.{host:"localhost",port:"3010"}
   * @param cb{Function}    初始化完成后回调
   */
  pomelo.init = function(params, cb){
    var host = params.host;
    var port = params.port;

    encode = params.encode || defaultEncode;
    decode = params.decode || defaultDecode;

    console.log('encode: ' + !!params.encode);

    var url = 'ws://' + host;
    if(port) {
      url +=  ':' + port;
    }

    ws_url = url;

    handshakeBuffer.user = params.user;
    handshakeCallback = params.handshakeCallback;
    initWebSocket(url, cb);
  };

  /**
   * 断开连接的函数
   */
  pomelo.disconnect = function() {
    try{      
      if(socket) {
        if(socket.disconnect) socket.disconnect();
        if(socket.close) socket.close();

        console.log("【网络】断开游戏服务器连接");

        clearSocket(socket);
        socket           = null;
      }

      if(heartbeatId) {
        clearTimeout(heartbeatId);
        heartbeatId = null;
      }
      if(heartbeatTimeoutId) {
        clearTimeout(heartbeatTimeoutId);
        heartbeatTimeoutId = null;
      }
    } 
    catch(e){
      console.log("【网络】" + e);
    }
  };

  /** 
   * 发送请求，会有结果返回
   * @param route{String} 协议路由
   * @param msg{Object}   消息,如果定义了protobuf，则require字段必须有数据
   * @param cb{Function}  消息处理函数,参数是json数据 cb(json)
   */
  pomelo.request = function(route, msg, cb) {
    if(arguments.length === 2 && typeof msg === 'function') {
      cb = msg;
      msg = {};
    } else {
      msg = msg || {};
    }
    route = route || msg.route;
    if(!route) {
      return;
    }

    reqId++;
    sendMessage(reqId, route, msg);

    callbacks[reqId] = cb;
    routeMap[reqId] = route;
  };

  /**
   * 给服务端发送通知
   * @param route{String} 协议路由
   * @param msg{Object}   消息,如果定义了protobuf，则require字段必须有数据
   */
  pomelo.notify = function(route, msg) {
    msg = msg || {};
    sendMessage(0, route, msg);
  };

  pomelo.getPing = function(){
    return heartbeatPing;
  }

  /** 设置握手数据（暂未使用） */
  pomelo.setHandshake = function(protos){
    pomeloProtos = protos;
  }

  /** 数据解码 */
  var defaultDecode = pomelo.decode = function(data) {
    //probuff decode
    var msg = Message.decode(data);

    if(msg.id > 0){
      msg.route = routeMap[msg.id];
      delete routeMap[msg.id];
      if(!msg.route){
        console.log("【网络】协议路由不存在");
        return;
      }
    }

    msg.body = deCompose(msg);
    return msg;
  };

  var defaultEncode = pomelo.encode = function(reqId, route, msg) {
    var type = reqId ? Message.TYPE_REQUEST : Message.TYPE_NOTIFY;

    //compress message by protobuf
    if(clientProtos && clientProtos[route]) {
      msg = Protobuf.encode(route, msg);
    } else {
      msg = Protocol.strencode(JSON.stringify(msg));
    }

    var compressRoute = 0;
    if(dict && dict[route]) {
      route = dict[route];
      compressRoute = 1;
    }

    return Message.encode(reqId, type, compressRoute, route, msg);
  };

  var clearSocket = function(socket){
    var nope = function() {};

    socket.onopen    = nope;
    socket.onmessage = nope;
    socket.onerror   = nope;
    socket.onclose   = nope;
  }

  var initWebSocket = function(url,cb) {
    initCallback = cb;

    //Add protobuf version
    if (protoVersion === 0){
      var protos;

      try{
        // 外部设置的数据比本地保存的数据优先级高
        if(pomeloProtos){
          protos = pomeloProtos;
        }
        // else if(localStorage && localStorage.getItem('protos')){
        //   protos = JSON.parse(localStorage.getItem('protos'));
        // }
      }
      catch(err){
        protos = null;
      }

      if(protos){
        protoVersion = protos.version || 0;
        serverProtos = protos.server || {};
        clientProtos = protos.client || {};

        if(Protobuf) Protobuf.init({encoderProtos: clientProtos, decoderProtos: serverProtos});
      }
    }
    
    //Set protoversion
    handshakeBuffer.sys.protoVersion = protoVersion;

    var createSocket = function(){
      pomelo.disconnect();

      socket            = new WebSocket(url);
      socket.binaryType = 'arraybuffer';
      socket.onopen     = onopen;
      socket.onmessage  = onmessage;
      socket.onerror    = onerror;
      socket.onclose    = onclose;
    };

    /** 连接成功 */
    var onopen = function(event){
      closeCount = 0; 
      connected  = true;
      console.log("【网络】连接成功");
      var obj = Package.encode(Package.TYPE_HANDSHAKE, Protocol.strencode(JSON.stringify(handshakeBuffer)));
      send(obj);
    };

    /** 接收数据 */
    var onmessage = function(event) {
      processPackage(Package.decode(event.data), cb);
      // new package arrived, update the heartbeat timeout
      if(heartbeatTimeout) {
        nextHeartbeatTimeout = Date.now() + heartbeatTimeout; 
      }
    };

    /** 网络错误 */
    var onerror = function(event) {
      // pomelo.emit(Events.IOERROR, event); 

      console.log("【网络】连接游戏服务器出错");
    };

    /** 网络断开 */
    var onclose = function(event){
      if (connected == false && closeCount < 3){
        closeCount++;
        console.log("【网络】WEBSOCKET 重握手");
        createSocket();
      }
      else {
        console.log("【网络】WEBSOCKET 断开");
        closeCount = 0;
        connected  = false;
        socket     = null;
        pomelo.emit(Events.CLOSE);
      }
    };

    if(socket){
      clearSocket(socket);
    }

    console.log('【网络】游戏服务器连接地址：' + url);

    createSocket();
  };

  /** 向服务器发消息 */
  var sendMessage = function(reqId, route, msg) {
    if(encode) {
      msg = encode(reqId, route, msg);
    }

    var packet = Package.encode(Package.TYPE_DATA, msg);
    send(packet);
  };

  /** 向服务器发消息 */
  var send = function(packet){
    if(socket == null || socket.readyState != WS_OPEN){
      console.log("【网络】发送数据给服务器时检查与服务器已断开");
      // pomelo.emit(Events.CLOSE);
    } 
    else {
      socket.send(packet);
    }
  };

  var handler = {};

  var heartbeat = function(data) {
    if(heartbeatStartTime){ 
      heartbeatPing = (new Date()) - heartbeatStartTime;
    }

    if(!heartbeatInterval) {
      // no heartbeat
      return;
    }

    var obj = Package.encode(Package.TYPE_HEARTBEAT);
    if(heartbeatTimeoutId) {
      clearTimeout(heartbeatTimeoutId);
      heartbeatTimeoutId = null;
    }

    if(heartbeatId) {
      // already in a heartbeat interval
      return;
    }

    heartbeatId = setTimeout(function() {
      if (socket == null || socket.readyState != WS_OPEN) return;

      heartbeatId = null;
      heartbeatStartTime = new Date();
      send(obj);

      nextHeartbeatTimeout = Date.now() + heartbeatTimeout;
      heartbeatTimeoutId   = setTimeout(heartbeatTimeoutCb, heartbeatTimeout);

    }, heartbeatInterval);
  };

  var heartbeatTimeoutCb = function() {
    var gap = nextHeartbeatTimeout - Date.now();
    if(gap > gapThreshold) {
      heartbeatTimeoutId = setTimeout(heartbeatTimeoutCb, gap);
    } else {
      pomelo.emit(Events.HEARTBEAT_TIMEOUT);
      pomelo.disconnect();
    }
  };

  var handshake = function(data){
    data = JSON.parse(Protocol.strdecode(data));
    if(data.code === RES_OLD_CLIENT) {
      pomelo.emit(Events.ERROR, 'client version not fullfill');
      return;
    }

    if(data.code !== RES_OK) {
      pomelo.emit(Events.ERROR, 'handshake fail');
      return;
    }

    handshakeInit(data);

    var obj = Package.encode(Package.TYPE_HANDSHAKE_ACK);
    send(obj);
    if(initCallback) {
      initCallback(socket);
      initCallback = null;
    }
  };

  var onData = function(data) {
    var msg = data;
    if(decode) {
      msg = decode(msg);
    }
    processMessage(pomelo, msg);
  };

  var onKick = function (data) {
    data = Protocol.strdecode(data);

    try {
      data = JSON.parse(data);
    } catch(e) { 
      console.log(e.message);
    }
    pomelo.emit(Events.ONKICK, data);
  };


  handlers[Package.TYPE_HANDSHAKE] = handshake;
  handlers[Package.TYPE_HEARTBEAT] = heartbeat;
  handlers[Package.TYPE_DATA] = onData;
  handlers[Package.TYPE_KICK] = onKick;

  var processPackage = function(msg) {
    handlers[msg.type](msg.body);
  };

  var processMessage = function(pomelo, msg) {
    if(!msg.id) {
      // server push message
      pomelo.emit(msg.route, msg.body);
      return;
    }

    //if have a id then find the callback function with the request
    var cb = callbacks[msg.id];

    delete callbacks[msg.id];
    if(typeof cb !== 'function') {
      return;
    }

    try{
      cb(msg.body);
    }
    catch(e){
      pomelo.emit(Events.PARSE_ERROR,e);
    }
    
    return;
  };

  var processMessageBatch = function(pomelo, msgs) {
    for(var i=0, l=msgs.length; i<l; i++) {
      processMessage(pomelo, msgs[i]);
    }
  };

  var deCompose = function(msg){
    var route = msg.route;

    // Decompose route from dict
    if(msg.compressRoute) {
      if(!abbrs[route]){
        return {};
      }

      route = msg.route = abbrs[route];
    }
    if (serverProtos && serverProtos[route]){
      return Protobuf.decode(route, msg.body);
    }
    else{
      // cc.warn("【网络】协议需要使用 protobuf 压缩：" + route);
      return JSON.parse(Protocol.strdecode(msg.body));
    }
  };

  var handshakeInit = function(data){
    if(data.sys && data.sys.heartbeat) {
      heartbeatInterval = data.sys.heartbeat * 1000;   // heartbeat interval
      heartbeatTimeout = heartbeatInterval * 2;        // max heartbeat timeout
    } else {
      heartbeatInterval = 0;
      heartbeatTimeout = 0;
    }

    initData(data);

    if(typeof handshakeCallback === 'function') {
      handshakeCallback(data.user);
    }
  };

  //Initilize data used in pomelo client
  var initData = function(data){
    if(!data || !data.sys) {
      return;
    }
    dict = data.sys.dict;
    var protos = data.sys.protos;

    //Init compress dict
    if(dict){
      dict = dict;
      abbrs = {};

      for(var route in dict){
        abbrs[dict[route]] = route;
      }
    }

    //Init Protobuf protos
    if(protos){
      protoVersion = protos.version || 0;
      serverProtos = protos.server || {};
      clientProtos = protos.client || {};
      if(!!Protobuf){
        Protobuf.init({encoderProtos: protos.client, decoderProtos: protos.server});
      }

      //Save protobuf protos to localStorage
      // localStorage.setItem('protos', JSON.stringify(protos));
    } else {
      // localStorage.setItem('protos', JSON.stringify({}));
    }
  };

  module.exports = pomelo;
})();
